/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.coach;

import java.util.Vector;

import robocup.component.AbstractAgent;
import robocup.component.PConf;
import robocup.component.SConf;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.geometry.Vektor;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.CoachBallInfo;
import robocup.component.infotypes.CoachPlayerInfo;
import robocup.component.infotypes.CoachVisualInfo;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.speechacts.MessageFactory;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * The <code>CoachWorld</code> captures the actual knowledge about the world
 * of the coach/trainer. Since these agents perceive the world without noise,
 * their world-model is very accurate, and its update is very easy in contrast
 * to that of the player agents, because the positions and velocities are given
 * directly.<br><br>
 * 
 * The structure of the coach-world is less complicated, future work may be to
 * implement a super-type for all world-models, such that certain code can be 
 * reused in both.
 */
public class CoachWorld {

	/**
	 * A history of received information (visual and aural).
	 */
	private CoachFactBase myFactBase;

	/**
	 * A reference to the agent class. may be Coach- or TrainerAgent.
	 */
	private AbstractAgent agent;

	/**
	 * The actual game cycle.
	 */
	private int visualCycle = -1;

	/**
	 * The actual play-mode.
	 */
	private PLAY_MODE playMode = PLAY_MODE.BEFORE_KICK_OFF;

	/**
	 * The number of goals we shot.
	 */
	private int ownGoals = 0;

	/**
	 * The number of goals the opponents shot.
	 */
	private int oppGoals = 0;

	/**
	 * An array holding all actual information concerning the teammates.
	 */
	private Player[] teammates = new Player[11];

	/**
	 * An array holding all actual information concerning the opponents.
	 */
	private Player[] opponents = new Player[11];

	/**
	 * The ball.
	 */
	private Ball ball = null;

	/**
	 * All server-settings and constants.
	 */
	private SConf sConf;

	/**
	 * An array holding all available player-types [0..6]. These are received 
	 * from the server (in several PlayerTypeInfos).
	 */
	private PConf[] playerTypes = null;

	/**
	 * A factory for creating messages (strings) out of objects.
	 */
	private MessageFactory mf = null;

	/**
	 * The actual serverCycle, which is incremented each time, a CoachVisualInfo
	 * arrives, and hence is generally larger than the <code>visualCycle</code>,
	 * because this is the game cycle, which is not incremented in pausing
	 * play-modes (e.g. before-kickoff).
	 */
	private int serverCycle = -1;

	/**
	 * A counter for cycles, which have play-mode play on. Is always resetted
	 * to zero, if the playmode changed to another type.
	 */
	private int playOnCycle = 0;

	/**
	 * <code>CoachWorld</code> constructor, creates instances of all players
	 * (own and opponents), the ball and the messageFactory, and references 
	 * the SConf. Note that there are always eleven players per team, although
	 * some may contain dummy data, if they don't exist.
	 * 
	 * @param factBase - a new instance of the CoachfactBase
	 */
	public CoachWorld(CoachFactBase factBase) {
		this.myFactBase = factBase;
		for (int i = 0; i < 11; i++) {
			this.teammates[i] = new Player(-1, -1, i + 1, new Vektor(Vektor.XY,
					-999, -999), new Vektor(), 0, 0, 0, 0, 0, false, true,
					false, new PConf());
			this.opponents[i] = new Player(-1, -1, i + 1, new Vektor(Vektor.XY,
					-999, -999), new Vektor(), 0, 0, 0, 0, 0, false, false,
					false, new PConf());
		}
		this.ball = new Ball(0, 0, new Vektor(), new Vektor());
		this.sConf = SConf.getInstance();
		mf = new MessageFactory();
	}

	/**
	 * <code>setParent</code> sets the reference to the agent object.
	 * 
	 * @param agent - the agent object, to which the coach-world belongs.
	 */
	public void setAgent(AbstractAgent agent) {
		this.agent = agent;
	}

	/**
	 * Retrieves the agent instance
	 */
	public AbstractAgent getAgent() {
		return agent;
	}

	/**
	 * <code>update</code> - updates the knowledge of the coach/trainer due to
	 * new information received by a CoachVisualInfo. This updates the players,
	 * the ball, the playmode and cycles (visual/server/playOn) and the 
	 * factBase.
	 * 
	 * @param cvi - the latest visual information
	 */
	public void update(CoachVisualInfo cvi) {
		if (cvi != null) {
			this.myFactBase.update(cvi);

			this.visualCycle = cvi.getCycle();
			this.serverCycle++;
			if (this.playMode == PLAY_MODE.PLAY_ON)
				this.playOnCycle++;
			else
				this.playOnCycle = 0;
			if (this.playOnCycle == (this.sConf.FREEFORM_SEND_PERIOD + this.sConf.FREEFORM_WAIT_PERIOD))
				this.playOnCycle = this.sConf.FREEFORM_SEND_PERIOD;
			// this.teammates = new CoachPlayerInfo[11];
			// this.opponents = new CoachPlayerInfo[11];

			CoachPlayerInfo[] temp = cvi.getOwnPlayers();
			// System.out.println("CoachWorld teammates: " + temp.length);
			for (int i = 0; i < temp.length; i++) {

				int nr = temp[i].getPlayerNumber() - 1;
				if (nr < 11) {
					// update the players
					this.teammates[nr].setCycle(this.visualCycle);
					this.teammates[nr].setBodyDir(temp[i].getBodyAngle());
					this.teammates[nr].setNeckAngle(temp[i].getNeckAngle());
					//this.teammates[nr].setPointDirection(temp[i]
					//		.getPointDirection());
					this.teammates[nr].getPosition().x = temp[i].getXPos();
					this.teammates[nr].getPosition().y = temp[i].getYPos();
					this.teammates[nr].getSpeed().x = temp[i].getXSpeed();
					this.teammates[nr].getSpeed().y = temp[i].getYSpeed();
					this.teammates[nr].setGoalie(temp[i].isGoalie());
				} else {
					System.out
							.println("CoachWorld received teammate with (number > 11):"
									+ nr);
				}
			}

			temp = cvi.getOtherPlayers();
			for (int i = 0; i < temp.length; i++) {
				int nr = temp[i].getPlayerNumber() - 1;
				if (nr < 11) {
					// update the opps
					this.opponents[nr].setCycle(this.visualCycle);
					this.opponents[nr].setBodyDir(temp[i].getBodyAngle());
					this.opponents[nr].setNeckAngle(temp[i].getNeckAngle());
					this.opponents[nr].getPosition().x = temp[i].getXPos();
					this.opponents[nr].getPosition().y = temp[i].getYPos();
					this.opponents[nr].getSpeed().x = temp[i].getXSpeed();
					this.opponents[nr].getSpeed().y = temp[i].getYSpeed();
					this.opponents[nr].setGoalie(temp[i].isGoalie());
				} else {
					System.out
							.println("CoachWorld received opponent with (number > 11):"
									+ nr);
				}
			}

			// update the ball
			CoachBallInfo cbi = cvi.getCoachBall();
			this.ball.setCycle(cvi.getCycle());
			this.ball.getPosition().x = cbi.getXPos();
			this.ball.getPosition().y = cbi.getYPos();
			this.ball.getSpeed().x = cbi.getXSpeed();
			this.ball.getSpeed().y = cbi.getYSpeed();

		}
	}

	/**
	 * The <code>update</code>-method for aural information changes
	 * the play-mode or the goals, which were sent by the server.
	 * All infos were forwarded to the CoachFactBase.
	 * 
	 * @param info - the latest aural info
	 */
	public void update(AuralInfo info) {
		if (info instanceof PlayModeInfo) {
			this.myFactBase.update(info);
			PlayModeInfo pmi = (PlayModeInfo) info;
			this.playMode = pmi.getPlayModeType();
			if (this.playMode == PLAY_MODE.GOAL_OTHER) {
				this.oppGoals++;
			}
			if (this.playMode == PLAY_MODE.GOAL_OWN) {
				this.ownGoals++;
			}
		}
	}

	/**
	 * <code>getPlayerPosition</code> returns the position of a
	 * certain player.
	 * 
	 * @param number
	 *            number of the player
	 * @param isFriend
	 *            true if player is a friendly, false otherwise
	 * @return Vektor - position of the player
	 */
	public Vektor getPlayerPosition(int number, boolean isFriend) {
		return (isFriend) ? getTeammatePosition(number)
				: getOpponentPosition(number);
	}

	/**
	 * <code>getTeammatePosition</code> returns the position of a 
	 * certain teammate.
	 * 
	 * @param number
	 *            number of the player
	 * @return Vektor - position of the friendly player
	 */
	public final Vektor getTeammatePosition(int number) {
		if (this.teammates[number - 1] == null) {
			return null;
		}
		return this.teammates[number - 1].getPosition().cloned();
	}

	/**
	 * <code>getOpponentPosition</code> returns the position of a 
	 * certain opponent.
	 * 
	 * @param number
	 *            number of the player
	 * @return Vektor - position of the enemy player
	 */
	public final Vektor getOpponentPosition(int number) {
		if (this.opponents[number - 1] == null) {
			return null;
		}

		return this.opponents[number - 1].getPosition().cloned();
	}

	/**
	 * <code>getPlayerSpeed</code> returns the speed of a 
	 * certain player
	 * 
	 * @param number
	 *            number of the player
	 * @param isFriend
	 *            true if player is a friendly, false otherwise
	 * @return Vektor - speed of the player
	 */
	public Vektor getPlayerSpeed(int number, boolean isFriend) {
		if (number > 11)
			return null;
		Player p = null;
		if (isFriend) {
			p = this.teammates[number - 1];
		} else {
			p = this.opponents[number - 1];
		}
		return p.getSpeed().cloned();
	}

	/**
	 * <code>getBallPosition</code> returns the position of the ball.
	 *  
	 * @return Vektor - position of the ball
	 */
	public final Vektor getBallPosition() {
		return this.ball.getPosition().cloned();
	}

	/**
	 * Getter for the ball. 
	 * 
	 * @return A copy of the ball-position.
	 */
	public Ball getBall() {
		return this.ball.cloned();
	}

	/**
	 * <code>getBallVelocity</code>
	 * 
	 * @return Vektor - returns a copy of the velocity of the ball
	 */
	public final Vektor getBallVelocity() {
		return this.ball.getSpeed().cloned();
	}

	/**
	 * <code>getPlayerBodyAngle</code> returns a certain players body
	 * angle.
	 * 
	 * @param number
	 *            number of the player
	 * @param isFriend
	 *            true if player is a friendly, false otherwise
	 * @return double - body angle of the player
	 */
	public double getPlayerBodyAngle(int number, boolean isFriend) {
		// return (isFriend) ? getTeammateBA(number) : getOpponentBA(number);
		if (number > 11)
			return 0;
		Player p = null;
		if (isFriend) {
			p = this.teammates[number - 1];
		} else {
			p = this.opponents[number - 1];
		}
		return p.getBodyDir();
	}

	/**
	 * <code>getPlayerNeckAngle</code> retrieves the nack-angle of
	 * a specified player.
	 * 
	 * @param number
	 *            number of the player
	 * @param isFriend
	 *            true if player is a friendly, false otherwise
	 * @return double - neck angle of the player
	 */
	public double getPlayerNeckAngle(int number, boolean isFriend) {
		return (isFriend) ? 
				getTeammateNeckAngle(number) : 
					getOpponentNeckAngle(number);
	}

	/**
	 * <code>getTeammateNeckAngle</code> retrieves the nackangle
	 * of a specified teammate.
	 * 
	 * @param number
	 *            number of the player
	 * @return double - neck angle of the friendly player
	 */
	public final double getTeammateNeckAngle(int number) {
		if (this.teammates[number - 1] == null) {
			return Double.NaN;
		}

		return this.teammates[number - 1].getNeckAngle();
	}

	/**
	 * <code>getOpponentNeckAngle</code> retrieves teh neckangle of
	 * a specified opponent.
	 * 
	 * @param number
	 *            number of the player
	 * @return double - neck angle of the enemy player
	 */
	public final double getOpponentNeckAngle(int number) {
		if (this.opponents[number - 1] == null) {
			return Double.NaN;
		}

		return this.opponents[number - 1].getNeckAngle();
	}

	/**
	 * <code>getVisualCycle</code> getter
	 * 
	 * @return Returns the visualCycle, which coresponds to the game
	 * cycle.
	 */
	public final int getVisualCycle() {
		return this.visualCycle;
	}

	/**
	 * <code>getTeammatesCount</code> returns the number of teammates
	 * 
	 * @return int - number of teammates on field
	 */
	public final int getTeammatesCount() {
		int count = 0;
		for (int i = 0; i < this.teammates.length; i++) {
			if (this.teammates[i] != null) {
				count++;
			}
		}
		return count;
	}

	/**
	 * <code>getOpponentsCount</code> returns the number of opponents
	 * 
	 * @return int - number of enemies on field
	 */
	public final int getOpponentsCount() {
		int count = 0;
		for (int i = 0; i < this.opponents.length; i++) {
			if (this.opponents[i] != null) {
				count++;
			}
		}
		return count;
	}

	/**
	 * <code>setPlayMode</code> setter
	 * 
	 * @param pMode
	 *            The playMode to set.
	 */
	public void setPlayMode(PLAY_MODE pMode) {
		this.playMode = pMode;
	}

	/**
	 * <code>getPlayMode</code> getter
	 * 
	 * @return Returns the playMode.
	 */
	public PLAY_MODE getPlayMode() {
		return this.playMode;
	}

	/**
	 * <code>getPlayersPositions</code> returns a Vector containing all
	 * positions of the players (teammates and opponents). 
	 * 
	 * @return Vector<Vektor> - positions of all players on the field
	 */
	public Vector<Vektor> getPlayersPositions() {
		Vector<Vektor> positions = new Vector<Vektor>();
		for (int i = 1; i < 12; i++) {
			Vektor pos = getTeammatePosition(i);
			if (pos != null) {
				positions.add(new Vektor(Vektor.XY, pos.x, pos.y));
			}
			pos = getOpponentPosition(i);
			if (pos != null) {
				positions.add(new Vektor(Vektor.XY, pos.x, pos.y));
			}
		}
		return positions;
	}

	/**
	 * <code>existsPlayer</code> checks, if a specified player
	 * exists.
	 * 
	 * @param nr
	 *            number of the player
	 * @param friendly
	 *            true if player is a friendly, false otherwise
	 * @return boolean - true if the player exists, false otherwise
	 */
	public boolean existsPlayer(int nr, boolean friendly) {
		return (friendly) ? this.teammates[nr - 1].getCycle() >= 0
				: this.opponents[nr - 1].getCycle() >= 0;
	}

	/**
	 * <code>getTeamName</code> returns a team name of a team, depending on
	 * the <code>own</code> flag.
	 * 
	 * @param own
	 *            true if own team's name wanted, otherwise false
	 * @return String - team name
	 */
	public final String getTeamName(boolean own) {
		if (this.myFactBase != null) {
			CoachVisualInfo cvi = this.myFactBase.getCurrentVI();
			if (cvi != null) {
				return (own) ? cvi.getOwnTeamName() : cvi.getOtherTeamName();
			}
		}
		return null;
	}

	/**
	 * <code>getMyFactBase</code> getter
	 * 
	 * @return Returns the myFactBase.
	 */
	public final CoachFactBase getMyFactBase() {
		return this.myFactBase;
	}

	/**
	 * <code>getSConf</code> getter for the SConf
	 * 
	 * @return SConf - configuration of the server
	 */
	public final SConf getSConf() {
		return this.sConf;
	}

	/**
	 * Sets the local server-configuration. Note, that a global one
	 * exists by using SConf.getInstance().
	 * 
	 * @param conf - the new server configuration
	 */
	public void setSConf(SConf conf) {
		this.sConf = conf;
	}

	/**
	 * Getter for the opponents-array
	 * 
	 * @return an array containing all opponents.
	 */
	public Player[] getOpponents() {
		return this.opponents;
	}

	/**
	 * Sets a new complete array of opponents
	 * 
	 * @param opponents - the new player array
	 */
	public void setOpponents(Player[] opponents) {
		this.opponents = opponents;
	}

	/**
	 * Getter for the teammates-array
	 * 
	 * @return an array containing all opponents.
	 */
	public Player[] getTeammates() {
		return this.teammates;
	}

	/**
	 * Sets a new complete array of teammates
	 * 
	 * @param teammates - the new player array
	 */
	public void setTeammates(Player[] teammates) {
		this.teammates = teammates;
	}

	/**
	 * Sets a new ball.
	 * 
	 * @param ball - the new ball-object
	 */
	public void setBall(Ball ball) {
		this.ball = ball;
	}

	/**
	 * Sets a complete array of PlayerTypes, usually containing seven
	 * values, one for each player-type generated by the server.
	 * 
	 * @param playerTypes - the player-types array
	 */
	public void setPlayerTypes(PConf[] playerTypes) {
		this.playerTypes = playerTypes;
	}

	/**
	 * Retrieves all available player-types.
	 * 
	 * @return the array containing all player types
	 */
	public PConf[] getPlayerTypes() {
		return this.playerTypes;
	}

	/**
	 * Returns the number of goal shot by the opponents.
	 * 
	 * @return int - the amount 
	 */
	public int getOppGoals() {
		return this.oppGoals;
	}

	/**
	 * Returns the number of goal shot by ourself.
	 * 
	 * @return int - the amount 
	 */
	public int getOwnGoals() {
		return this.ownGoals;
	}

	/**
	 * Checks whether our team is located on the east side. Note that
	 * this is only known by the Coach, since the Trainer doesn't belong
	 * to any team.
	 * 
	 * @return true, if the team starts on east (right) side.
	 */
	public boolean isTeamEast() {
		return this.agent.isTeamEast();
	}

	/**
	 * Getter for the messageFactory. This should be used by all agents
	 * instead of creating a new instance.
	 * 
	 * @return the messageFactory
	 */
	public MessageFactory getMessageFactory() {
		return mf;
	}

	/**
	 * Returns the servercycle, which corresponds to the total number of visual
	 * information received. Since in the default setting a visual information 
	 * is received every cycle, this value is larger than the visualCycle (i.e.
	 * the actual game cycle).
	 * 
	 * @return int - the number of cycles passed during connection to the server
	 */
	public int getServerCycle() {
		return serverCycle;
	}

	/**
	 * The number of cycles, the actual play-on mode lasts. Is resetted to zero,
	 * if playmode is not play-on.
	 * 
	 * @return int - cycles
	 */
	public int getPlayOnCycle() {
		return playOnCycle;
	}

}
